#include "fifo_util.h"
#include <signal.h>
#include <errno.h>
#include <pthread.h>


#ifdef __linux__
//#define _POSIX_C_SOURCE 200112L // 确保POSIX功能可用
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/select.h>
#else
#include <windows.h>
#include <io.h>
#endif


#define READ_BY_SELECT_MODE (1) // 通过select模式读取数据
#define DATA_BUFFER_MAXSIZE (4 * 1024) // 有名管道数据单包最大值


/***************************************************************
 * 功能：select模式下精确睡眠
 * 输入参数：
 *      mill_sec：睡眠时间，毫秒值
 * 返回值：
 *      int：结果，0 -- 成功，<0 -- 失败
 * *************************************************************/
static int select_sleep(int mill_sec)
{
#ifdef __linux__
    struct timeval timeout = {0, mill_sec * 1000};   //{秒, 微秒}
    select(0, NULL, NULL, NULL, &timeout);
#else
    Sleep(mill_sec);
#endif
    return 0;
}


// 线程传输的数据类型
typedef struct _fifo_thread_arg{
    int fd; // 有名管道句柄
    int flag; // windows平台下使用到的标识，取值FIFO_SERVER或者FIFO_CLIENT
} fifo_thread_arg;

// 处理FIFO连接请求
static int fifoutil_runtag = 0; // 运行标识，0 -- 未运行，1 -- 运行中
static pthread_t fifoutil_thread_hd = 0; // 线程句柄
static struct _fifo_thread_arg fifoutil_arg; // FIFO线程传输参数

// 数据自动读取的回调和调用上下文，线程
static pfn_fifo_readdata_callback fifo_readdata_callback_pfn = NULL;
static void* fifo_call_context = NULL;


/***************************************************************
 * 功能：处理有名管道数据回调的线程执行函数
 * 输入参数：
 *      void*：线程输入信息，这里为有名管道的句柄
 * 返回值：
 *      void*：线程执行反馈的信息
 * *************************************************************/
static void* pfn_fifo_deal_connect(void* arg)
{
    pthread_setname_np(pthread_self(), "FIFORead"); // 设置线程名称

    struct _fifo_thread_arg* _arg = (struct _fifo_thread_arg *)arg;
    if (!_arg) {
        return NULL;
    }
    //printf("handle: %d, flag: %d.\r\n", _arg->fd, _arg->flag);

    char buffer[DATA_BUFFER_MAXSIZE] = {0};
#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
    HANDLE hPipe = (HANDLE)_arg->fd;
    int flag = _arg->flag;
    if (0 > hPipe) {
        return NULL;
    }
#else
    int fd = _arg->fd;
#if READ_BY_SELECT_MODE
    fd_set fd_read;
#endif // READ_BY_SELECT_MODE

#endif // defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)

    while(fifoutil_runtag) {
#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
        if (FIFO_SERVER == flag) {
            //printf("pfn_fifo_deal_connect: wait for client connect...\r\n");
            if (!ConnectNamedPipe(hPipe, NULL)) {
                DWORD dwError = GetLastError();
                if (dwError != ERROR_PIPE_CONNECTED && dwError != ERROR_IO_PENDING) {
                    select_sleep(5); // 让出CPU时间
                    continue;
                }
            }

            // 接收数据，并回调给应用层
            while (fifoutil_runtag) {
                memset(buffer, 0, sizeof (buffer)); // 清除上一次数据缓存
                DWORD dReadBytes = 0;
                if (ReadFile(hPipe, buffer, DATA_BUFFER_MAXSIZE - 1, &dReadBytes, NULL)) {
                    if (fifo_readdata_callback_pfn && 0 < dReadBytes) {
                        buffer[(int)dReadBytes] = '\0';
                        fifo_readdata_callback_pfn((int)hPipe, buffer, (size_t)dReadBytes, fifo_call_context);
                    }
                } else {
                    DWORD dwError = GetLastError();
                    if (ERROR_NO_DATA == dwError) {
                        // 没有数据可读，继续等待
                        continue;
                    } else if (ERROR_BROKEN_PIPE == dwError) {
                        // 对端断开了，跳出关闭
                        //printf("pfn_fifo_deal_connect: client disconnected.\r\n");
                        break;
                    } else {
                        printf("pfn_fifo_deal_connect: ReadFile failed with error: %lu.\r\n", dwError);
                        break;
                    }
                }
                select_sleep(2); // 让出CPU时间
            }

            // 断开连接
            DisconnectNamedPipe(hPipe);
        } else {
            memset(buffer, 0, sizeof (buffer)); // 清除上一次数据缓存
            DWORD dReadBytes = 0;
            if (ReadFile(hPipe, buffer, DATA_BUFFER_MAXSIZE - 1, &dReadBytes, NULL)) {
                if (fifo_readdata_callback_pfn && 0 < dReadBytes) {
                    buffer[(int)dReadBytes] = '\0';
                    fifo_readdata_callback_pfn((int)hPipe, buffer, (size_t)dReadBytes, fifo_call_context);
                }
            }
        }
        select_sleep(2); // 让出CPU时间
#else
#if READ_BY_SELECT_MODE
        FD_ZERO(&fd_read);
        FD_SET(fd, &fd_read);

        int ret = select(fd + 1, &fd_read, NULL, NULL, NULL);
        if (ret > 0 && FD_ISSET(fd, &fd_read)) {
#endif // READ_BY_SELECT_MODE
            memset(buffer, 0, sizeof (buffer)); // 清除上一次数据缓存
            ssize_t read_size = read(fd, buffer, (DATA_BUFFER_MAXSIZE - 1));
            if (0 < read_size) {
                buffer[read_size] = '\0';
                if (fifo_readdata_callback_pfn) {
                    fifo_readdata_callback_pfn(fd, buffer, (size_t)read_size, fifo_call_context);
                }
            }
#if READ_BY_SELECT_MODE
        }
#endif // READ_BY_SELECT_MODE
#endif
        select_sleep(2); // 让出CPU时间

    }

    return NULL;
}


#ifdef __linux__
/******************************************************************
    * 功能：创建有名管道
    *   注：多进程通信时，有名管道需遵循谁创建谁销毁的原则；文件路径目录需要存在。
    * 输入参数：
    *      fifo_path：通信双方约定的相同有名管道路径信息
    * 返回值：
    *      int：操作结果，小于0 -- 失败，0 -- 成功
    * ****************************************************************/
int fifoutil_create(const char* fifo_path)
{
    if (!fifo_path) {
        perror("fifoutil_create: input fifo path is nullptr.\r\n");
        return -1;
    }

    // 先检查文件是否存在
    if (0 == access(fifo_path, F_OK)) {
        unlink(fifo_path); // 删除现有文件
    }

    if (0 > mkfifo(fifo_path, 0666) && EEXIST != errno) {
        printf("fifoutil_create: create fifo path (%s) failed.\r\n", fifo_path);
        return -1;
    }

    // 更新权限
    if (0 > chmod(fifo_path, 0666)) {
        printf("fifoutil_create: change fifo path (%s) permission failed.\r\n", fifo_path);
        return -1;
    }

    return 0;
}

/******************************************************************
 * 功能：销毁有名管道，注：多进程通信时，有名管道需遵循谁创建谁销毁的原则
 * 输入参数：
 *      fifo_path：通信双方约定的相同有名管道路径信息
 * ****************************************************************/
void fifoutil_destroy(const char* fifo_path)
{
    if (!fifo_path) {
        perror("fifoutil_destroy: input fifo path is nullptr.\r\n");
        return;
    }

    if (0 > unlink(fifo_path)) {
        printf("fifoutil_destroy: unlink fifo path (%s) failed.\r\n", fifo_path);
    }

    return;
}
#endif

/******************************************************************
 * 功能：打开有名管道，需先创建再打开
 * 输入参数：
 *      fifo_path：通信双方约定的相同有名管道路径信息
 *      flags：操作权限标识
 * 注：linux平台上如O_WRONLY、O_RDONLY、或者与O_NONBLOCK进行或运算，如O_RDONLY | O_NONBLOCK，O_WRONLY | O_NONBLOCK
 * 注：windows平台管道的服务端使用FIFO_SERVER，客户端使用FIFO_CLIENT。
 * 返回值：
 *      int：有名管道的文件句柄fd信息，小于0 -- 失败，大于等于0 -- 成功
 * ****************************************************************/
int fifoutil_open(const char* fifo_path, int flags)
{
    if (!fifo_path) {
        perror("fifoutil_open: input fifo path is nullptr.\r\n");
        return -1;
    }

    int fd = -1;
#ifdef __linux__
    fd = open(fifo_path, flags); // 正确返回值范围0 - OPEN_MAX
    if (0 > fd) {
        printf("fifoutil_open: open fifo path (%s) failed.\r\n", fifo_path);
        perror("fifoutil_open.\r\n");
        return -1;
    }

    signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号，避免读端关闭后导致写端退出
#else
    char szPipeName[128] = {0};
    snprintf(szPipeName, sizeof (szPipeName), "\\\\.\\pipe\\%s", fifo_path);

    //DWORD dOpenMode = PIPE_ACCESS_DUPLEX; // 双向访问
    DWORD dPipeMode = /*PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | */PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT;


    //dOpenMode |= GENERIC_READ;
    //dOpenMode |= GENERIC_WRITE;

    if (FIFO_SERVER == flags) {
        // 创建命名管道（服务端）
        DWORD dOpenMode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; // 双向访问，非阻塞
        HANDLE hPipe = CreateNamedPipe(szPipeName, dOpenMode
                                       , dPipeMode
                                       , PIPE_UNLIMITED_INSTANCES // 最大实例数
                                       , 4096 // 输出缓冲区大小
                                       , 4096 // 输入缓冲区大小
                                       , 0 // 默认超时：0s
                                       , NULL // 默认安全属性
                                       );
        fd = (int)hPipe;
    } else {
        // 客户端模式
        HANDLE hPipe = CreateFile(szPipeName, GENERIC_READ | GENERIC_WRITE, 0 // 不共享
                                  , NULL // 默认安全属性
                                  , OPEN_EXISTING, FILE_FLAG_OVERLAPPED // 设置为非阻塞模式
                                  , NULL);
        //WaitNamedPipe(szPipeName, 5000);
        fd = (int)hPipe;
    }
#endif
    return fd;
}

/******************************************************************
 * 功能：关闭有名管道
 * 输入参数：
 *      fd：打开有名管道时获取的文件句柄
 * ****************************************************************/
void fifoutil_close(int fd)
{
    if (fd) {
#ifdef __linux__
        close(fd);
#else
        CloseHandle((HANDLE)fd);
#endif
    }
    return;
}

/******************************************************************
 * 功能：设置有名管道为非阻塞模式
 * 输入参数：
 *      fd：打开有名管道时获取的文件句柄
 * 返回值：
 *      int：操作结果，小于0 -- 失败，0 -- 成功
 * ****************************************************************/
int fifoutil_set_nonblocking(int fd)
{
    if (0 > fd) {
        return -1;
    }
#ifdef __linux__
    int flags = fcntl(fd, F_GETFL, 0);
    if (0 > flags) {
        perror("fifoutil_set_nonblocking: fcntl F_GETFL.\r\n");
        return -1;
    }

    flags |= O_NONBLOCK;
    if (0 > fcntl(fd, F_SETFL, flags)) {
        perror("fifoutil_set_nonblocking: fcntl F_SETFL.\r\n");
        return -1;
    }
#else
    HANDLE hPipe = (HANDLE)fd;
    DWORD dwMode = PIPE_NOWAIT;
    if (!SetNamedPipeHandleState(hPipe, &dwMode, NULL, NULL)) {
        printf("fifoutil_set_nonblocking: failed to set pipe to non-blocking mode. rrror: %lu.\r\n", GetLastError());
        return -1;
    }
#endif

    return 0;
}

/******************************************************************
 * 功能：设置有名管道处理数据回调函数
 * 输出参数：
 *      pfn：回调函数
 *      context：调用的上下文实例
 * ****************************************************************/
void fifoutil_set_readdata_callback(pfn_fifo_readdata_callback pfn, void *context)
{
    fifo_readdata_callback_pfn = pfn;
    fifo_call_context = context;
    return;
}

/******************************************************************
 * 功能：开始有名管道自动读取数据的线程
 * 输出参数：
 *      fd：打开有名管道时获取的文件句柄
 *      flag：运行的标识，windows平台上使用，取值FIFO_SERVER或者FIFO_CLIENT
 * ****************************************************************/
void fifoutil_loop_start(int fd, int flag)
{
    if (1 == fifoutil_runtag) {
        perror("fifoutil_loop_start: it is already started, please call stop first.\r\n");
        return;
    }

    fifoutil_arg.fd = fd;
    fifoutil_arg.flag = flag;
    //printf("handle: %d, flag: %d.\r\n", fifoutil_arg.fd, fifoutil_arg.flag);

    fifoutil_runtag = 1; // 设置开始标识
    if (0 != pthread_create(&fifoutil_thread_hd, NULL, pfn_fifo_deal_connect, &fifoutil_arg)) {
        perror("fifoutil_loop_start: create thread failed.\r\n");
        return;
    }

    return;
}

/******************************************************************
 * 功能：停止有名管道自动读取数据的线程
 * ****************************************************************/
void fifoutil_loop_stop()
{
    if (1 == fifoutil_runtag) {
        fifoutil_runtag = 0; // 设置停止标识
        pthread_join(fifoutil_thread_hd, NULL);
        fifoutil_thread_hd = 0;
    }

    return;
}

/******************************************************************
 * 功能：从有名管道中读取数据
 * 输入参数：
 *      fd：有名管道的fd
 *      buffer：读取数据的缓存空间
 *      buf_size：数据缓存空间的大小
 * 返回值：
 *      int：读取结果，大于等于0 -- 成功，具体读取数据的大小，-1 -- 不成功
 * ****************************************************************/
int fifoutil_read(int fd, char* buffer, size_t buf_size)
{
    if (0 > fd || !buffer || 0 >= buf_size) {
        return -1;
    }
#ifdef __linux__
    ssize_t read_size = read(fd, buffer, (buf_size - 1));
#if 0
    // 不用提示消息
    if (0 > read_size) {
        perror("fifoutil_read: read data failed.\r\n");
        return -1;
    }
#endif
    buffer[read_size] = '\0';
    return (int)read_size;
#else
    HANDLE hPipe = (HANDLE)fd;
    DWORD dReadBytes = 0;

    if (ReadFile(hPipe, buffer, (DWORD)(buf_size - 1), &dReadBytes, NULL)) {
        if (0 < dReadBytes) {
            buffer[dReadBytes] = '\0';
        }
        return (int)dReadBytes;
    }
#if 0
    DWORD err = GetLastError();
    if (ERROR_BROKEN_PIPE == err || ERROR_PIPE_NOT_CONNECTED == err) {
        perror("fifoutil_read: read data failed.\r\n");
    }
#endif
    return -1;
#endif
}

/******************************************************************
 * 功能：把数据写入有名管道中
 * 输入参数：
 *      fd：有名管道的fd
 *      buffer：待写入数据的缓存空间
 *      size：数据大小
 * 返回值：
 *      int：写入结果，0 -- 成功，非0 -- 不成功
 * ****************************************************************/
int fifoutil_write(int fd, const char* buffer, size_t size)
{
    if (0 > fd || !buffer) {
        return -1;
    }
#ifdef __linux__
    if (0 > write(fd, buffer, size)) {
        if (EPIPE == errno) {
            perror("fifoutil_write: reader is closed.\r\n");
        } else if (EAGAIN == errno) {
            perror("fifoutil_write: FIFO is full, please call reader to read data.\r\n");
        }
        perror("fifoutil_write: write data failed.\r\n");
        return -1;
    }
    return 0;
#else
    HANDLE hPipe = (HANDLE)fd;
    DWORD dWriteBytes = 0;

    if (WriteFile(hPipe, buffer, (DWORD)size, &dWriteBytes, NULL)) {
        return 0;
    }
#if 0
    DWORD err = GetLastError();
    if (ERROR_NO_DATA == err || ERROR_BROKEN_PIPE == err || ERROR_PIPE_NOT_CONNECTED == err) {
        perror("fifoutil_write: write data failed.\r\n");
    }
#endif
    return -1;
#endif
}
